package cn.chiship.framework.common.config.interceptor;

import cn.chiship.framework.common.service.GlobalCacheService;
import cn.chiship.framework.common.util.FrameworkUtil2;
import cn.chiship.sdk.cache.service.RedisService;
import cn.chiship.sdk.cache.service.UserCacheService;
import cn.chiship.sdk.core.annotation.Authorization;
import cn.chiship.sdk.core.annotation.NoParamsSign;
import cn.chiship.sdk.core.annotation.NoVerificationAppIdAndKey;
import cn.chiship.sdk.core.annotation.NoVerificationProjectId;
import cn.chiship.sdk.core.base.BaseResult;
import cn.chiship.sdk.core.base.constants.BaseConstants;
import cn.chiship.sdk.core.enums.BaseResultEnum;
import cn.chiship.sdk.core.encryption.EncryptionUtil;
import cn.chiship.sdk.core.encryption.ProofUtil;
import cn.chiship.sdk.core.enums.HeaderEnum;
import cn.chiship.sdk.core.id.SnowflakeIdUtil;
import cn.chiship.sdk.core.util.OutUtil;
import cn.chiship.sdk.core.util.StringUtil;
import cn.chiship.sdk.core.util.ip.IpUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import cn.chiship.framework.common.annotation.SystemOptionAnnotation;
import cn.chiship.framework.common.constants.CommonCacheConstants;
import com.google.common.base.Strings;
import cn.chiship.framework.common.util.UserPermissionsUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;


/**
 * 自定义拦截器，对请求进行身份验证
 * 2017-11-03
 *
 * @author lijian
 */
public class AuthorizationInterceptor implements HandlerInterceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationInterceptor.class);

    private static final String PROF_PROJECT_ID = "projectId";
    private static final String PROF_APP_SECRET = "appSecret";
    private static final String PREFIX_MD5 = "MD5=";
    private static final int MD5_PREFIEX_ENCRYPT_LEGTH = 4;
    private UserCacheService userCacheService;
    private RedisService redisService;
    private String publicKey = null;
    private String privateKey = null;

    /**
     * 鉴权信息的无用前缀，默认为空
     */
    private String httpHeaderPrefix = "";
    private String encryptionCode = "";


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
        if (response.getStatus() == BaseConstants.STATUS_NOT_FOUND) {
            return writeError(response, BaseResult.error(BaseResultEnum.NOT_FOUND, null));
        }
        /**
         * 如果不是映射到方法直接通过
         */
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        if (FrameworkUtil2.getGlobalProperties().isDemoEnabled() && BaseConstants.HTTP_REQUEST_TYPE_POST.equals(request.getMethod())) {
            String whiteList = FrameworkUtil2.getGlobalProperties().getInterfaceWhite();
            if (StringUtil.isNullOrEmpty(whiteList)) {
                return writeError(response, BaseResult.error(BaseResultEnum.SYSTEM_ERROR, "演示环境不能操作，如需了解请加入QQ群：778332363!"));
            }
            if (!StringUtil.strToListString(whiteList, ",").contains(method.getName())) {
                return writeError(response, BaseResult.error(BaseResultEnum.SYSTEM_ERROR, "演示环境不能操作，如需了解请加入QQ群：778332363!"));
            }
        }
        redisService = factory.getBean(RedisService.class);
        GlobalCacheService globalCacheService = factory.getBean(GlobalCacheService.class);
        String ip = IpUtils.getIpAddr(request);
        BaseResult baseResult = globalCacheService.validateLoginIp(ip);
        if (!baseResult.isSuccess()) {
            return writeError(response, baseResult);
        }

        verificationAppIdAndKey(request, response, handlerMethod);
        verificationParamsSign(request, response, handlerMethod);
        verificationAuthorization(request, response, handlerMethod, factory);


        /**
         * 为了解决统一拦截异常信息保存日志
         */
        SystemOptionAnnotation systemOption = method.getAnnotation(SystemOptionAnnotation.class);
        if (systemOption != null) {
            request.setAttribute("systemOption", systemOption);
        }
        request.setAttribute("methodName", method.getDeclaringClass().getName() + "." + method.getName() + "()");
        request.setAttribute("ip", IpUtils.getIpAddr(request));
        request.setAttribute("requestId", SnowflakeIdUtil.generateId());

        /**
         * 检测用户是否拥有权限或角色
         */


        return UserPermissionsUtil.checkOption(method, userCacheService, request);
    }

    /**
     * 验证App-Id 和App-Key是否有效
     * 如果验证App-Id和App-Key失败，并且方法注明了NoAppKeyAndSecret 不开启验证
     * 查看方法上是否有注解
     * 查看方法所在的Controller是否有注解
     */
    public boolean verificationAppIdAndKey(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        Method method = handlerMethod.getMethod();
        /**
         * 验证ProjectId
         */
        Boolean isVerificationProjectId = false;
        String projectId = null;
        if (method.getAnnotation(NoVerificationProjectId.class) == null && handlerMethod.getBeanType().getAnnotation(NoVerificationProjectId.class) == null) {
            isVerificationProjectId = true;
            projectId = request.getHeader(HeaderEnum.HEADER_PROJECTS_ID.getName());
            if (Strings.isNullOrEmpty(projectId)) {
                projectId = request.getParameter(HeaderEnum.HEADER_PROJECTS_ID.getName());
                if (Strings.isNullOrEmpty(projectId)) {
                    return writeError(response, BaseResult.error(BaseResultEnum.HEADER_NO_PROJECTS_ID_PARAM, null));
                }
            }
        }
        if (method.getAnnotation(NoVerificationAppIdAndKey.class) == null && handlerMethod.getBeanType().getAnnotation(NoVerificationAppIdAndKey.class) == null) {
            String appId = request.getHeader(HeaderEnum.HEADER_APP_ID.getName());
            String appKey = request.getHeader(HeaderEnum.HEADER_APP_KEY.getName());
            if (Strings.isNullOrEmpty(appKey) || Strings.isNullOrEmpty(appId)) {
                return writeError(response, BaseResult.error(BaseResultEnum.HEADER_NO_APP_KEY_AND_APP_ID, null));
            } else {
                JSONObject prof = (JSONObject) redisService.hget(CommonCacheConstants.buildKey(CommonCacheConstants.REDIS_PROOF_PREFIX), appId);
                if (prof != null) {
                    if (Boolean.TRUE.equals(isVerificationProjectId) && !projectId.equals(prof.getString(PROF_PROJECT_ID))) {
                        return writeError(response, BaseResult.error(BaseResultEnum.ERROR_PROJECTS_ID, null));
                    }

                    /**
                     * 随机安全码
                     */
                    encryptionCode = prof.getString("signKey");

                    /**
                     * 公钥
                     */
                    publicKey = prof.getString("publicKey");

                    /**
                     * 私钥
                     */
                    privateKey = prof.getString("privateKey");

                    /**
                     * 通过消息头获得AppId和AppKey 反向解密变成appSecret
                     */
                    if (!ProofUtil.verifyAppSecret(appId, appKey, encryptionCode, prof.getString(PROF_APP_SECRET))) {
                        return writeError(response, BaseResult.error(BaseResultEnum.ERROR_APP_KEY, null));
                    }
                } else {
                    return writeError(response, BaseResult.error(BaseResultEnum.ERROR_APP_ID, null));
                }
            }
        }
        return true;
    }

    /**
     * 验证参数签名
     * 如果验证Sign失败，并且方法注明了NoEncryption 不开启加密验签
     * 查看方法上是否有注解
     * 查看方法所在的Controller是否有注解
     */
    public boolean verificationParamsSign(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        Method method = handlerMethod.getMethod();
        if (method.getAnnotation(NoParamsSign.class) == null && handlerMethod.getBeanType().getAnnotation(NoParamsSign.class) == null) {
            String encrypt = request.getHeader(HeaderEnum.HEADER_SIGN.getName());
            if (!Strings.isNullOrEmpty(encrypt)) {
                if (!PREFIX_MD5.equals(encrypt.substring(0, MD5_PREFIEX_ENCRYPT_LEGTH))) {
                    boolean signIsPass = EncryptionUtil.rsaVerifySignature(request, encryptionCode, publicKey);
                    LOGGER.info("验签结果:{}", signIsPass);
                    if (!signIsPass) {
                        return writeError(response, BaseResult.error(BaseResultEnum.SIGN_ERR, null));
                    }
                } else {
                    encrypt = encrypt.substring(4);
                }


            } else {
                return writeError(response, BaseResult.error(BaseResultEnum.HEADER_NO_SIGN_PARAM, null));
            }
        }
        return true;
    }

    /**
     * 校验用户是否登录
     * 如果验证token失败，并且方法注明了Authorization
     * 查看方法上是否有注解
     * 查看方法所在的Controller是否有注解
     */
    public boolean verificationAuthorization(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, BeanFactory factory) throws Exception {
        Method method = handlerMethod.getMethod();

        if (method.getAnnotation(Authorization.class) != null || handlerMethod.getBeanType().getAnnotation(Authorization.class) != null) {
            /**
             * 从header中得到token
             */
            String token = request.getHeader(HeaderEnum.HEADER_ACCESS_TOKEN.getName());
            if (token != null && token.startsWith(httpHeaderPrefix) && token.length() > 0) {
                userCacheService = factory.getBean(UserCacheService.class);
                if (!userCacheService.isValid()) {
                    return writeError(response, BaseResult.error(BaseResultEnum.ERROR_TOKEN, null));
                }
            } else {
                return writeError(response, BaseResult.error(BaseResultEnum.HEADER_NO_TOKEN, null));
            }
        }
        return true;
    }

    public boolean writeError(HttpServletResponse response, BaseResult baseResult) throws Exception {
        OutUtil.writeJson(response, baseResult);
        return false;
    }


}
